package com.learn.search.dao;

import com.alibaba.fastjson.JSON;
import com.learn.search.entity.ElasticEntity;

import com.learn.eduapi.entity.PageEntity;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.support.master.AcknowledgedResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.indices.CreateIndexRequest;
import org.elasticsearch.client.indices.CreateIndexResponse;
import org.elasticsearch.client.indices.GetIndexRequest;
import org.elasticsearch.common.document.DocumentField;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.reindex.BulkByScrollResponse;
import org.elasticsearch.index.reindex.DeleteByQueryRequest;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.FieldSortBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;

@Slf4j
@Repository
public class ElasticSearchDAO {

    @Autowired
    private RestHighLevelClient client;

    /**
     * 判断索引是否存在
     * @param indexName
     * @return
     * @throws IOException
     */
    public boolean existsIndex(String indexName) throws IOException {
        GetIndexRequest request = new GetIndexRequest(indexName);
        boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);
        log.info("{} exists:{}",indexName,exists);
        return exists;
    }

    /**
     * 创建索引
     * @param indexName
     * @throws IOException
     */
    public void createIndex(String indexName) throws IOException {
        CreateIndexRequest createRequest = new CreateIndexRequest(indexName);
        CreateIndexResponse response = client.indices().create(createRequest, RequestOptions.DEFAULT);
        log.info("{}创建成功 {}",indexName, JSON.toJSONString(response));
    }

    /**
     * 删除索引
     * @param indexName
     * @throws IOException
     */
    public void deleteIndex(String indexName) throws IOException {
        DeleteIndexRequest request = new DeleteIndexRequest(indexName);
        AcknowledgedResponse response = client.indices().delete(request, RequestOptions.DEFAULT);
        log.info("{}删除成功 {}",indexName, JSON.toJSONString(response));
    }

    /**
     * 添加或更新数据
     */
    public void saveOrUpdate(String indexName,ElasticEntity entity) throws IOException {
        IndexRequest request = new IndexRequest(indexName);
        request.id(entity.getId()).source(JSON.toJSONString(entity.getData()), XContentType.JSON);
        IndexResponse response = client.index(request, RequestOptions.DEFAULT);
        log.info("{}添加或更新数据成功 {}",indexName, JSON.toJSONString(response));
    }

    /**
     * 查询对象
     */
    public <T> List<T> search(String indexName, SearchSourceBuilder builder, Class<T> c) throws IOException {
        SearchRequest request = new SearchRequest(indexName);
        request.source(builder);
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        SearchHit[] hits = response.getHits().getHits();
        List<T> res = new ArrayList<>(hits.length);
        for (SearchHit hit : hits) {
            res.add(JSON.parseObject(hit.getSourceAsString(), c));
        }
        return res;
    }

    /**
     * 通用分页搜索方法
     * @param indexName 索引名称
     * @param from 开始行号
     * @param size 查询长度
     * @param map 查询条件集合
     * @param sort 排序方式集合
     * @param c 实体类型
     * @param <T>
     * @return
     * @throws IOException
     */
    public <T> PageEntity<T> searchPageByMap(String indexName, int from, int size,
                                             Map<String,String> map,
                                             Map<String,String> sort,
                                             Class<T> c,
                                             String... highlightFields) throws IOException {
        SearchRequest request = new SearchRequest(indexName);
        SearchSourceBuilder builder = new SearchSourceBuilder();
        //执行布尔查询，通过多个条件进行过滤
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        if(map != null && map.size() > 0) {
            for (String key : map.keySet()) {
                String value = map.get(key);
                //如果条件内容不为空，按该条件进行过滤
                if(StringUtils.isNotEmpty(value)) {
                    boolQueryBuilder.filter(QueryBuilders.matchPhraseQuery(key, value));
                }
            }
        }
        //设置查询条件
        if(boolQueryBuilder.filter().size() > 0) {
            builder.query(boolQueryBuilder);
        }
        //执行排序字段和方式
        if(sort != null && sort.size() > 0){
            String field = sort.get("field");
            String type = sort.get("type");
            builder.sort(new FieldSortBuilder(field).order(SortOrder.fromString(type)));
        }
        //设置高亮查询
        HighlightBuilder hBuilder = new HighlightBuilder();
        for(String fieldName : highlightFields){
            hBuilder.field(fieldName);
        }
        hBuilder.preTags("<span style='color:red'>").postTags("</span>");
        builder.highlighter(hBuilder);
        //指定分页位置和长度
        builder.from(from);
        builder.size(size);
        request.source(builder);
        //处理查询结果
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        SearchHit[] hits = response.getHits().getHits();
        List<T> res = new ArrayList<>();
        for (SearchHit hit : hits) {
            T obj = JSON.parseObject(hit.getSourceAsString(), c);
            res.add(obj);
            //替换普通属性值为高亮属性值
            Map<String, HighlightField> hFields = hit.getHighlightFields();
            for(String hField : hFields.keySet()){
                try {
                    Class<?> aClass = obj.getClass();
                    Field declaredField = aClass.getDeclaredField(hField);
                    Text[] fragments = hFields.get(hField).fragments();
                    if(declaredField != null && fragments.length > 0) {
                        declaredField.setAccessible(true);
                        declaredField.set(obj, fragments[0].string());
                    }
                } catch (NoSuchFieldException | IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
        //包装到分页实体类中
        PageEntity<T> entity = new PageEntity<>();
        entity.setData(res);
        entity.setTotal((int) response.getHits().getTotalHits().value);
        entity.setPageSize(size);
        return entity;
    }

    /**
     * 批量插入数据
     */
    public void insertBatch(String indexName, List<ElasticEntity> list) throws IOException {
        BulkRequest request = new BulkRequest();
        list.forEach(item -> request.add(new IndexRequest(indexName).id(item.getId())
                .source(JSON.toJSONString(item.getData()), XContentType.JSON)));
        BulkResponse response = client.bulk(request, RequestOptions.DEFAULT);
        log.info("{}批量插入数据成功 {}",indexName, JSON.toJSONString(response));
    }

    /**
     * 通过id批量删除
     */
    public <T> void deleteBatch(String indexName, Collection<T> idList) throws IOException {
        BulkRequest request = new BulkRequest();
        idList.forEach(item -> request.add(new DeleteRequest(indexName, item.toString())));
        BulkResponse response = client.bulk(request, RequestOptions.DEFAULT);
        log.info("{}批量删除数据成功 {}",indexName, JSON.toJSONString(response));
    }

    /**
     * 通过条件删除对象
     */
    public void deleteByQuery(String indexName, QueryBuilder builder) throws IOException {
        DeleteByQueryRequest request = new DeleteByQueryRequest(indexName);
        request.setQuery(builder);
        BulkByScrollResponse response = client.deleteByQuery(request, RequestOptions.DEFAULT);
        log.info("{}批查询删除数据成功 {}",indexName, JSON.toJSONString(response));
    }
}
